#include "environment.h"
#include <iostream>
#include <stdlib.h>
#include "animal.h"
#include "predator.h"
#include "prey.h"

using namespace std;
	
int Environment::mapSizeX = 11;
int Environment::mapSizeY = 11;

Environment::Environment() {
	predStart=0;
	preyStart=0;
	print = false;
}

// Deletes both the predator and prey lists
Environment::~Environment() {
	clearWrapperLists();
}

// Sets the print mode for the simulations
void Environment::setPrint( bool printMode ) {
	print = printMode;
}

// Deletes the wrapper lists, but keeps the animals in memory
void Environment::clearWrapperLists() {

	if ( predStart ) {
		while( predStart->next != 0 ) {
			predStart = predStart->next;
			delete predStart->prev;
		}
		delete predStart;
		predStart = 0;
	}

	if ( preyStart ) {
		while( preyStart->next != 0 ) {
			preyStart = preyStart->next;
			delete preyStart->prev;
		}
		delete preyStart;
		preyStart = 0;
	}
}

// Useful method to deallocate all the agents in the environment
void Environment::deleteAllAgents() {
	AnimalWrapper * helper;
	
	helper = predStart;
	while( helper ) {
		delete helper->animal;
		helper = helper->next;
	}
	
	helper = preyStart;
	while( helper ) {
		delete helper->animal;
		helper = helper->next;
	}

	// Finishes by deleting all the wrappers
	clearWrapperLists();
}

// Adds a new predator to the predator list
void Environment::addPredator(Predator * pred, int x, int y) {
	addAnimal(pred, x, y, 0);
}

// Adds a new prey to the prey list
void Environment::addPrey(Prey * prey, int x, int y) {
	addAnimal(prey, x, y, 1 );
}

// Adds an animal to one of the agents lists
void Environment::addAnimal(Animal * an, int x, int y, int type) {
	AnimalWrapper ** helper;
	if ( type == 0 ) helper = &predStart;
	else if ( type == 1 ) helper = &preyStart;
	else return;

	AnimalWrapper * newAnimal = new AnimalWrapper();
	newAnimal->animal = an;
	newAnimal->startPosition[0]=x;
	newAnimal->startPosition[1]=y;
	newAnimal->position[0]=x;
	newAnimal->position[1]=y;
	newAnimal->next=0;

	if( ! (*helper) ) {
		*helper = newAnimal;
		newAnimal->prev=0;
	}
	else{
		while((*helper)->next != 0)	{
			// Check if we don't already own this animal in the list
			// The user must instantiate different animals, can't use the same more than once!
			if ( (*helper)->animal == an ) {
				delete newAnimal;
				return;
			}
			helper = &((*helper)->next);
		}
		// Last element check
		if ( (*helper)->animal == an ) {
			delete newAnimal;	
			return;
		}

		(*helper)->next = newAnimal;
		newAnimal->prev= *helper;
	}
}

// Information retriever for preys
int Environment::isPredatorNearby( Prey * prey ) {
	AnimalWrapper * helper = preyStart;
	while ( helper ) {
		if ( helper->animal == prey )
			return checkNearAnimals(preyStart, predStart);
		helper = helper->next;
	}
	return -1;
}

// Returns the relative position of the second animal from the point of view of the first animal
// -1 if they are not near
int Environment::checkNearAnimals( AnimalWrapper * animal1, AnimalWrapper * animal2) {
	// animal1 is located vertically to the animal2
	if( (animal1->position[0] == animal2->position[0]) &&
		// And they are next to each other
		(abs(animal1->position[1] - animal2->position[1])%9==1)) {
		if(animal1->position[1] < animal2->position[1]) {
			if ( animal1->position[1] + animal2->position[1] != 10)
				return 0;
			else
				return 2;
		}
		if(animal2->position[1] < animal1->position[1]) {
			if ( animal2->position[1] + animal1->position[1] != 10)
				return 2;
			else
				return 0;
		}
	}

	// animal1 is located horizontally to the animal2
	if( (animal1->position[1] == animal2->position[1]) &&
		// And they are next to each other
		(abs(animal1->position[0] - animal2->position[0])%9==1)) {
		if(animal1->position[0] < animal2->position[0]) {
			if ( animal1->position[0] + animal2->position[0] != 10)
				return 1;
			else
				return 3;
		}
		if(animal2->position[0] < animal1->position[0]) {
			if ( animal2->position[0] + animal1->position[0] != 10)
				return 3;
			else
				return 1;
		}
	}
	return -1;
}

// Resets the position for all the animals as they were first put in the environment
void Environment::resetAgentsPositions() {
	AnimalWrapper * current = predStart;
	while(current != 0) {
		current->position[0]=current->startPosition[0];
		current->position[1]=current->startPosition[1];
		current = current->next;
	}

	current = preyStart;
	while(current != 0) {
		current->position[0]=current->startPosition[0];
		current->position[1]=current->startPosition[1];
		current = current->next;
	}
}

// The actual simulation cycle
int Environment::startSimulation() {
	// Stop Simulation if no animals exist
	if ( ! predStart || ! preyStart ) return -1; 

	int time = 1;
	AnimalWrapper * helper;
	int counter, move;

	while (1) {
		if ( print ) cout << "***** TIME " << time << " *****\n";

		// Moving all the predators
		helper = predStart;
		counter = 1;

		while(helper) {
			if ( print ) cout << "Predator " << counter << ":\t" << helper -> position[0]
                 << "," << helper -> position[1] <<"\t->\t";
            // Predator executes move
			move = helper->animal->move();
			// Position updated in the environment
			updatePosition(helper, move);

			if ( print ) cout <<	helper -> position[0] << ","
                 << helper -> position[1] << "\tMove " << move << "\n";
            //Next predator
			helper = helper -> next;
			counter++;
		}

		// Checking if the simulation is over
		// If there are more predators this will probably be moved into the while cycle
		if( (predStart->position[0] == preyStart->position[0]) &&
			(predStart->position[1] == preyStart->position[1]) ) {
			if ( print ) cout << "\nThe Predator has reached the Prey!\n";
			// Gives a reward to the predator
			((Predator*)predStart->animal)->addReward(10);
			// Resets all the animals in the environment at their starting position
			resetAgentsPositions();
			return time;
		}

		// Moving all the preys
		helper=preyStart;
		counter = 1;
		while(helper) {
			if ( print ) cout << "Prey " << counter << ":\t\t" << helper->position[0]
                 << "," << helper->position[1] << "\t->\t";
			// Prey executes move
			move = helper->animal->move();
			// Environment updates prey position
			updatePosition(helper, move);
			if ( print ) cout <<	helper -> position[0] << ","
                 << helper -> position[1] << "\tMove " << move << "\n";
            // Next prey
			helper = helper->next;
			counter++;
		}

		time++;
	}	// Time cycle
	return time;
}

// Moves an animal based on its move
void Environment::updatePosition(AnimalWrapper * animal, int direction) {
	switch(direction) {
		case 0:	animal->position[1]=(animal->position[1]+1)%mapSizeY;
				break;
		case 1:	animal->position[0]=(animal->position[0]+1)%mapSizeX;
				break;
		case 2:	animal->position[1]=(animal->position[1]+(mapSizeY-1))%mapSizeY;
				break;
		case 3: animal->position[0]=(animal->position[0]+(mapSizeX-1))%mapSizeX;
				break;
		default:;
			// We don't move
	}
}

